package blasthouse.states;

import blasthouse.characters.Eggman;
import blasthouse.characters.Mob;
import blasthouse.characters.Player;
import blasthouse.effects.Ability;
import blasthouse.effects.Attack;
import blasthouse.objects.CollisionObject;
import blasthouse.objects.CollisionWaypoint;
import blasthouse.objects.UpdatingGameElement;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.KeyListener;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.ShapeRenderer;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
 
public class GamePlayState extends BasicGameState implements KeyListener{	
	public static final Vector2f GRAVITY = new Vector2f(0, 0.3f);								// Hardcoded variables for player movement
	public static final float FRICTION = 0.008f;
	public static final float TERMINAL_VELOCITY = 7f;	
	public static final int GRID_INTERVAL = 120;
	public static double MIN_WAYPOINT_DISTANCE = 400;
	public static Vector<UpdatingGameElement> GameObjects = new Vector<UpdatingGameElement>();	// Should contain all collidables
	public static Vector<UpdatingGameElement> Actors = new Vector<UpdatingGameElement>();		// Holds the players (only 1 for now)	
	public static Vector<CollisionWaypoint> waypointGrid = new Vector<CollisionWaypoint>();
	public static Vector<CollisionWaypoint> superGrid = new Vector<CollisionWaypoint>();
	private Vector<UpdatingGameElement> DeleteBin = new Vector<UpdatingGameElement>();	
	private String[] mapArray;																	// Holds the mapfile chars
	private Image background = null, floorImage = null, wallImage = null, spikesImage = null; 	// Image holders	
	private Player myPlayer;																	// Holds player info
	private int stateID = -1, timer = 0;
	
    private Logger logger = Logger.getLogger("BlastHouse Logger");								// Unused so far
    
    
    GamePlayState( int stateID ) { this.stateID = stateID; }
 
    @Override
    public int getID() { return 3; }
 
    public void init(GameContainer gc, StateBasedGame sbg) throws SlickException {
    	background = new Image("blasthouse/images/snwmountain.jpg");	
    	floorImage = new Image("blasthouse/images/IceBlock.png");			// Set up the images we use for terrain
    	wallImage = new Image("blasthouse/images/IceWall.png");
    	spikesImage = new Image("blasthouse/images/IceSpikes.png");    	
    	
    	myPlayer = new Player();										// Setup initial player
    	myPlayer.setPlayerSpriteSheet(new SpriteSheet( new Image("blasthouse/images/Full-Idle.png"), 190, 160, 1, 1) );
    	myPlayer.setPlayerBounds(190, 160);
    	myPlayer.addAbility( new Ability("GorillaYell", 2, 0, 4, 200) );
    	myPlayer.addAbility( new Ability("GorillaJump", 1, 0, 5, 200) );
    	    	
    	Attack gorillaPunch = new Attack("GorillaPunch", 4, 0, 5, 150);	//Setup an attack with hitboxes
    	gorillaPunch.addHitFrame(new Rectangle(0,0,1,1));
    	gorillaPunch.addHitFrame(new Rectangle(0, 25, 50, 55));    
    	gorillaPunch.addHitFrame(new Rectangle(93, 84, 87, 69));
    	gorillaPunch.addHitFrame(new Rectangle(125, 15, 65, 106));
    	gorillaPunch.addHitFrame(new Rectangle(134, -1, 55, 91));
    	gorillaPunch.addHitFrame(new Rectangle(0,0,1,1));
    	
    	myPlayer.addAbility( gorillaPunch );
    	
    	myPlayer.addPlayerStat("jumpstrength", -8f);
    	myPlayer.addPlayerStat("jumpcount", 2f);
    	myPlayer.addPlayerStat("hp", 100f);
    	myPlayer.addPlayerStat("speed", 0.50f);
    	myPlayer.addPlayerStat("strength", 14f);	
    	myPlayer.addPlayerStat("topspeed", 5.0f);
		
    	Actors.add(myPlayer);
		
		loadMap();					// Creates map and collisionwaypointprofile		
    }
    
    public void render(GameContainer gc, StateBasedGame sbg, Graphics g) throws SlickException {    	
    	background.draw(0, 0);		// Background first, farthest back ZOrder, doesn't move with level
    	
    	// Keeps "camera" centered on character
    	gc.getGraphics().translate( -1*(myPlayer.getPlayerSpriteX() - gc.getScreenWidth()/4), -1*(myPlayer.getPlayerSpriteY() - gc.getScreenHeight()/4));    	
		    	
    	for( UpdatingGameElement prop : GameObjects){ 
    		if(prop instanceof CollisionObject){
    			((CollisionObject)prop ).render(gc, sbg, g); 	// Render all shapes, blocks, etc
    			ShapeRenderer.draw(((CollisionObject)prop ).getBounds());
    		}
    	}
    	
    	// TODO: use similar implementation for mobs?
    	for( UpdatingGameElement p : Actors ){ 
    		p.render(gc, sbg, g);	// Render for all players
    		if( p instanceof Mob ){ ShapeRenderer.draw(((Mob)p).getBounds()); }
    	}
    	
    	ShapeRenderer.draw(myPlayer.getBounds());	// Shows bounds for testing
    }
    
    public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException {    	
    	timer += delta;
    	
    	for( UpdatingGameElement p : Actors ){ 
    		p.update(gc, sbg, delta);			// Update all players and mobs involved  
    		if(p instanceof Mob){
    			if( ((Mob)p).isMarkForDelete() ){
    				DeleteBin.add(p);			// Collects unused mobs
    			}
    		}
    	}
    	
    	for(int i = 0; i < DeleteBin.size(); i++){
    		Actors.remove(DeleteBin.get(i));	// Removes references to unused mobs
    	}
    	
    	DeleteBin.clear();						// Empties the recycling bin
    }     
   
    public void loadMap(){
    	BufferedReader in;
    	double mapWidth = 0, mapHeight = 0;
    	InputStream is = getClass().getClassLoader().getResourceAsStream("LevelOne.txt");

    	    	
    	try{  		
            in = new BufferedReader(new InputStreamReader(is));
    		
    		mapWidth = Integer.parseInt(in.readLine());
    		mapHeight = Integer.parseInt(in.readLine());
    		
    		mapArray = new String[(int)mapHeight];
    		logger.info("Loaded map: ");
    		for(int n = 0; n < mapHeight; n++){
    			mapArray[n] = in.readLine();  
    			System.out.println(mapArray[n]);
    		}    		
    		
    	}catch(NumberFormatException e){ logger.log(Level.SEVERE, "Map File Format Problem", e); }
    	catch(FileNotFoundException e){ logger.log(Level.SEVERE, "Map File Not Found", e); }
    	catch(IOException e){ logger.log(Level.SEVERE, "Map File IO Error", e); }
    	    	
    	for(int i = 0; i < mapWidth; i++){
    		for(int n = 0; n < mapHeight; n++){
    			String marker = "_" + Integer.toString(n) + " " + Integer.toString(i);
    			switch(mapArray[n].charAt(i)){    				
    				case 'X':
    					GameObjects.add(new CollisionObject("wall"+marker, new Rectangle(i*GRID_INTERVAL, n*GRID_INTERVAL, 120, 120), wallImage));
    					break;
    				case '_':
    					GameObjects.add(new CollisionObject("platform"+marker, new Rectangle(i*GRID_INTERVAL, n*GRID_INTERVAL+(GRID_INTERVAL - 40), 120, 40), floorImage));
    					break;
    				case '0':
    					myPlayer.setPlayerSpriteX(i*GRID_INTERVAL + 60);
    					myPlayer.setPlayerSpriteY(n*GRID_INTERVAL + 60);
    					System.out.println("player at: " + myPlayer.getPlayerSpriteX() + ", " + myPlayer.getPlayerSpriteY());
    					break;
    				case '*':
    					GameObjects.add(new CollisionObject("spikes"+marker, new Rectangle(i*GRID_INTERVAL, n*GRID_INTERVAL, 120, 120), spikesImage));
    					break;
    				case '1':
    					Actors.add(new Eggman(i*GRID_INTERVAL+60, n*GRID_INTERVAL+60));    					
    					break;
    				default:
    					break;
    			}
    		}
    	}
    	
    	createCollisionWaypointMap(mapWidth, mapHeight);
    }
    
    public void createCollisionWaypointMap(double mapWidth, double mapHeight){
    	int numWaypointsWide =  (int)Math.ceil( ( mapWidth * GRID_INTERVAL / MIN_WAYPOINT_DISTANCE) );
    	int numWaypointsHigh =  (int)Math.ceil( ( mapHeight * GRID_INTERVAL / MIN_WAYPOINT_DISTANCE) );
    	CollisionWaypoint placer = new CollisionWaypoint();
    	
    	System.out.println(numWaypointsHigh + " nodes high and " + numWaypointsWide + " nodes wide.");
    	
    	// Setup tier 1 nodes
    	for(int i = 1; i <= numWaypointsWide; i++ ){
    		for(int n = 1; n <= numWaypointsHigh; n++ ){
    			placer.setPosition( i*(int)MIN_WAYPOINT_DISTANCE, n*(int)MIN_WAYPOINT_DISTANCE );
    			placer.populateList( (int)MIN_WAYPOINT_DISTANCE );
    			waypointGrid.add( placer );
    			placer = new CollisionWaypoint();
    		}
    	}
    	
    	// Setup brother lists, AFTER the whole list is made
    	for (CollisionWaypoint cwp : waypointGrid) {
			cwp.populateBrothers( (int)MIN_WAYPOINT_DISTANCE );
		}
    	
    	// Setup tier 2 nodes
    	for(int i = 1; numWaypointsWide - i > -1; i+=3 ){
    		for(int n = 1; numWaypointsHigh - n >= -1; n+=3 ){
    			placer.setPosition( i*(int)MIN_WAYPOINT_DISTANCE, n*(int)MIN_WAYPOINT_DISTANCE );
    			placer.populateChildren( (int)MIN_WAYPOINT_DISTANCE );
    			superGrid.add( placer );
    			placer = new CollisionWaypoint();
    		}
    	}    
    	
    	System.out.println("Created " + waypointGrid.size() + " tier 1 nodes.");
    	System.out.println("Created " + superGrid.size() + " tier 2 nodes.");
    }
    
    @Override
    public void keyPressed( int key, char c ){ myPlayer.keyPressed(key, c); }
    
    @Override
    public void keyReleased( int key, char c ){ myPlayer.keyReleased(key, c); }
}